#include "Subsystems/BlockBehaviors/SubsystemSaplingBlockBehavior.h"
#include "Log.h"
#include "GameSingleton.h"
#include "Components/ComponentMiner.h"
#include "Components/ComponentPlayer.h"
#include "Player/PlayerData.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Managers/PlantsManager.h"
#include "Subsystems/SubsystemWeather.h"
#include "Subsystems/SubsystemTimeOfDay.h"

#include "Blocks/SaplingBlock.h"
#include "Blocks/LeavesBlock.h"
#include "Blocks/DirtBlock.h"
#include "Blocks/GrassBlock.h"
#include "Blocks/WaterBlock.h"
#include "Blocks/DryBushBlock.h"

namespace PocketSurvival
{

    SubsystemSaplingBlockBehavior::SubsystemSaplingBlockBehavior()
    {
        m_handleBlocks.push_back(SaplingBlock::Index());
    }

    void SubsystemSaplingBlockBehavior::matureSapling(SaplingData &saplingData)
    {
        if (false == (m_gameInfo->totalElapsedGameTime >= saplingData.matureTime))
        {
            return;
        }
        int32_t x = saplingData.point.posX;
        int32_t y = saplingData.point.posY;
        int32_t z = saplingData.point.posZ;

        TerrainChunk *chunkAtCell = m_terrain->getTerrainChunk((x - 6) >> 4, (z - 6) >> 4);
        TerrainChunk *chunkAtCell2 = m_terrain->getTerrainChunk((x - 6) >> 4, (z + 6) >> 4);
        TerrainChunk *chunkAtCell3 = m_terrain->getTerrainChunk((x + 6) >> 4, (z - 6) >> 4);
        TerrainChunk *chunkAtCell4 = m_terrain->getTerrainChunk((x + 6) >> 4, (z + 6) >> 4);
        if (chunkAtCell != nullptr && chunkAtCell->State >= TerrainChunkState::InvalidLight &&
            chunkAtCell2 != nullptr && chunkAtCell2->State >= TerrainChunkState::InvalidLight &&
            chunkAtCell3 != nullptr && chunkAtCell3->State >= TerrainChunkState::InvalidLight &&
            chunkAtCell4 != nullptr && chunkAtCell4->State >= TerrainChunkState::InvalidLight)
        {
            int32_t cellContents = m_terrain->getCellContents(x, y - 1, z);
            if (cellContents == DirtBlock::Index() || cellContents == GrassBlock::Index())
            {
                if (Terrain::ExtractLight(m_terrain->getCellValue(x, y + 1, z)) >= 9)
                {
                    bool flag = false;
                    for (int32_t i = x - 1; i <= x + 1; i++)
                    {
                        for (int32_t j = z - 1; j <= z + 1; j++)
                        {
                            int32_t cellContents2 = m_terrain->getCellContents(i, y - 1, j);
                            if (GameSingleton::blocksManager->blocks[cellContents2]->isBlock<WaterBlock *>())
                            {
                                flag = true;
                                break;
                            }
                        }
                    }
                    float num;
                    if (saplingData.determineGrowth == true)
                    {
                        num = 1.0f;
                    }
                    else
                    {
                        int32_t num2 = m_terrain->getTemperature(x, z) + SubsystemWeather::GetTemperatureAdjustmentAtHeight(y);
                        int32_t num3 = m_terrain->getHumidity(x, z);
                        if (flag)
                        {
                            num2 = (num2 + 10) / 2;
                            num3 = MathUtils::Max<int32_t>(num3, 12);
                        }
                        num = 2.0f * GameSingleton::plantsManager->calculateTreeProbability(saplingData.type, num2, num3, y);
                    }
                    if (GameSingleton::gameRandom->getBool(num))
                    {
                        m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(0, 0, 0));
                        if (false == growTree(x, y, z, saplingData.type))
                        {
                            m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(DryBushBlock::Index(), 0, 0));
                        }
                    }
                    else
                    {
                        m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(DryBushBlock::Index(), 0, 0));
                    }
                }
                else if (m_gameInfo->totalElapsedGameTime > saplingData.matureTime + 1200.0)
                {
                    m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(DryBushBlock::Index(), 0, 0));
                }
            }
            else
            {
                m_terrain->changeCell(x, y, z, Terrain::MakeBlockValue(DryBushBlock::Index(), 0, 0));
            }
        }
        else
        {
            saplingData.matureTime = m_gameInfo->totalElapsedGameTime;
        }
    }

    bool SubsystemSaplingBlockBehavior::growTree(int32_t x, int32_t y, int32_t z, TreeType treeType)
    {
        std::vector<TerrainBrush> &treeBrushes = GameSingleton::plantsManager->getTreeBrushes(treeType);
        for (int32_t i = 0; i < 20; i++)
        {
            TerrainBrush terrainBrush = treeBrushes[GameSingleton::gameRandom->getInt(0, treeBrushes.size() - 1)];
            bool flag = true;
            for (auto &pair : terrainBrush.cellsMap)
            {
                if (pair.second.posY >= 0 && (pair.second.posX != 0 || pair.second.posY != 0 || pair.second.posZ != 0))
                {
                    int32_t cellContents = m_terrain->getCellContents(pair.second.posX + x, pair.second.posY + y, pair.second.posZ + z);
                    if (cellContents != 0 && false == GameSingleton::blocksManager->blocks[cellContents]->isBlock<LeavesBlock *>())
                    {
                        flag = false;
                        break;
                    }
                }
            }
            if (flag)
            {
                terrainBrush.paint(m_terrain, x, y, z);
                return true;
            }
        }
        return false;
    }

    void SubsystemSaplingBlockBehavior::addSapling(const SaplingData &saplingData)
    {
        m_saplingsLock.lock();
        m_saplings[saplingData.point] = saplingData;
        m_saplingsLock.unlock();
    }

    void SubsystemSaplingBlockBehavior::removeSapling(const Point3 &point)
    {
        m_saplingsLock.lock();
        auto it = m_saplings.find(point);
        if (it != m_saplings.end())
        {
            m_saplings.erase(it);
        }
        m_saplingsLock.unlock();
    }

    void SubsystemSaplingBlockBehavior::onNeighborBlockChanged(int32_t x, int32_t y, int32_t z, int32_t neighborX, int32_t neighborY, int32_t neighborZ)
    {
        int32_t cellContents = m_terrain->getCellContents(x, y - 1, z);
        if (GameSingleton::blocksManager->blocks[cellContents]->IsTransparent)
        {
            m_terrain->destroyCell(0, x, y, z, 0, false);
        }
    }

    void SubsystemSaplingBlockBehavior::onBlockAdded(int32_t value, int32_t oldValue, int32_t x, int32_t y, int32_t z)
    {
        float num = GameSingleton::gameRandom->getFloat(480.0f, 600.0f);
        SaplingData saplingData{
            Point3(x, y, z),
            (TreeType)Terrain::ExtractData(value),
            false,
            m_gameInfo->totalElapsedGameTime + num};
        addSapling(saplingData);
    }

    void SubsystemSaplingBlockBehavior::onBlockAdded(int32_t value, int32_t oldValue, int32_t x, int32_t y, int32_t z, ComponentMiner *miner)
    {
        if (miner->componentPlayer->playerData->gameMode != GameMode::Creative)
        {
            return;
        }
        m_saplingsLock.lock();
        auto it = m_saplings.find(Point3(x, y, z));
        if (it != m_saplings.end())
        {
            it->second.matureTime = m_gameInfo->totalElapsedGameTime + GameSingleton::gameRandom->getFloat(8.0f, 12.0f);
            it->second.determineGrowth = true;
        }
        m_saplingsLock.unlock();
    }

    void SubsystemSaplingBlockBehavior::onBlockRemoved(int32_t value, int32_t newValue, int32_t x, int32_t y, int32_t z)
    {
        removeSapling(Point3(x, y, z));
    }

    void SubsystemSaplingBlockBehavior::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(2.5f, 0.0f))
        {
            std::vector<SaplingData> saplingDataVec;
            m_saplingsLock.lock();
            for (auto &pair : m_saplings)
            {
                saplingDataVec.push_back(pair.second);
            }
            m_saplingsLock.unlock();

            for (auto &saplingData : saplingDataVec)
            {
                matureSapling(saplingData);
            }
        }
    }

    void SubsystemSaplingBlockBehavior::load(const nlohmann::json &json)
    {
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
    }

    void SubsystemSaplingBlockBehavior::save(nlohmann::json &json)
    {
    }

    static const std::string subsystemName = "SaplingBlockBehavior";
    const std::string &SubsystemSaplingBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemSaplingBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
}